MacApp supports several Macintosh user interface devices that allow a user to choose one item from a number of items. These devices include pull-down menus for choosing commands, the standard get-file dialog (SFGetFile) for choosing files, radio button groups for choosing among exclusive options, and checkboxes for choosing among non-exclusive options.
In this vein, MacApp also provides the TPopup class. When used as directed, TPopup provides a user-interface device for choosing among a fixed number of static items. But what if you want to give the user a choice among non-static items--items that vary in number and whose names can change at runtime? TPopup does not support this kind of dynamic behavior.
You could try a SFGetFile-type dialog, but that would mean losing the directness of TPopup’s click-and-choose behavior. You could use a scrollable list, but then you would lose TPopup’s visual compactness. So, to get the small screen space of a TPopup control and the dynamic listing behavior of a scrollable list, we want to create a subclass of TPopup, add some methods, and override the methods that get in our way.
Review: What’s a TPopup?
For those of you new to object-oriented programming with MacApp, let me provide some background. TPopup is one of MacApp’s many predefined classes. TPopup implements the standard Apple popup menu as defined in Inside Macintosh and Apple’s book “Human Interface Guidelines: The Apple Desktop Interface” (Addison-Wesley, 1987).
TPopup has its own methods (functionality) and fields (data), but it also inherits methods and fields from its ancestor classes. Moving up the hierarchy, TPopup’s ancestors are TControl, TView, TEvtHandler (i.e., TEventHandler), and TObject (from which all objects in MacApp derive).
Thus, TPopup is an object that can
• handle events through its inherited TEvtHandler functionality,
• activate or deactivate itself, show or hide itself, and enable or disable itself through its inherited TView functionality,
• dim or undim itself, track mouse clicks and mouse movement, and return a TCommand object through its inherited TControl functionality, and
• draw a popup menu in response to a mouse click, return the currently selected item number or text, and set the item selection to a given value through its own methods.
In other words, TPopup is a specialized TControl, which is a specialized TView, which is a specialized TEvtHandler.
Review: How Do You Customize TPopup’s Behavior?
Just as TPopup is a specialized TControl, TDynamicPopup is a specialized TPopup, because TDynamicPopup inherits from (or “is a subclass of”) TPopup. Therefore, the way you customize the behavior of TPopup is to define a new class that is a subclass of TPopup.
As part of this new class, you define new methods and fields that are required to provide the desired behavior but which are lacking in TPopup (and its ancestors).
In some cases, you will want to modify the behavior of a method inherited from TPopup or one of TPopup’s ancestors. To do so, you define a replacement method that overrides the inherited method. If you want to add functionality to an inherited method, you override it and call it directly (using the “Inherited” keyword) from within the new method’s implementation.
Introducing TDynamicPopup
TDynamicPopup is a subclass of TPopup that implements a popup menu for a dynamic list of items. In addition to standard TPopup methods, TDynamicPopup provides methods for
• adding, removing, and altering items while your application is running,
• selecting an item by its name or relative to the current selection, and
• returning the current number (count) of menu items, the cleared status of the menu (whether or not the menu has zero items), and whether a popup menu hit is actually an item-selection change.
When would you use a dynamic popup menu (“popmenu” for short)? Let me answer this through the use of a hypothetical application. Consider an analysis application in which the user can create and name any number of analysis components. Suppose that each such component has a number of values and attributes that are shown and editable in TDialogView views that take up a whole window.
Now, we want to give the user to be able to see and edit the values and attributes of any component in any order. We need a way to let the user pick among all the components quickly without having to remember or type names (of course). A dynamic popmenu is a user interface device that implements the desired functionality.
Using the TDynamicPopup Class
First, a caveat: the code given in the listings in this article is not quite commercial quality. It works well, but there are a few visual blemishes that I haven’t resolved yet. The “Blemishes” section later in this paper describes the problems.
In general, you can use a TDynamicPopup instance wherever you can use a TPopup instance. Usually, a TDynamicPopup control is a subview within a TDialogView instance.
Listing A shows the declaration for the TDynamicPopup class. The three instance variables (fields) are only for use within TDynamicPopup; consider them private. Among the methods, AdjustBotRight and DoRedraw should be considered private, but all the rest are public (non-TDynamicPopup code can use them freely).
The TDynamicPopup code assumes that you are creating views using resource templates. The next section describes how to specify the resources for TDynamicPopup instances. The subsequent sections describe TDynamicPopup methods, the implementations for which are given in Listing B.
Specifying TDynamicPopup Resources
To use a TDynamicPopup in your application, you can start with a dummy 'cmnu' resource as illustrated in Listing 1. This 'cmnu' resource is a dummy because you don’t specify any actual menu items; the actual menu items are established during program execution. Still, you must define a 'cmnu' resource to use the existing TPopup code.
Next, for each TDynamicPopup instance desired, define a Popup 'view' resource as in Listing 2. For the details of the TPopup resource specification, see the MacApp documentation, the DemoDialogs example application (supplied with MacApp), and the ViewTypes.r resource definition file.
The last number in the Popup 'view' resource represents the “Item left offset.” This number reserves horizontal space for the popmenu title, which, in turn, is specified in the 'cmnu' resource.
Listing 1 specifies a blank menu title (" ") and Listing 2 specifies that zero space be allowed for the popmenu’s title. You can use this approach, along with a TStaticText instance for the actual popmenu title, when you want a popmenu whose title doesn’t dim when you dim and disable the popmenu. (I dim and disable my popmenus when they have zero items.) However, if you use a TStaticText instance for the popmenu title, it will not highlight when the popmenu is showing as called for in the Apple Desktop Interface guidelines.
When you do want the title to dim with the popmenu itself, define a 'cmnu' resource with the desired title and specify the appropriate offset in the corresponding Popup 'view' resource.
TDynamicPopup uses the width specification in the Popup 'view' resource in a special way. The AdjustBotRight method uses this value (‘140’ in Listing 2) as the maximum width for the “current selection” box of the popmenu; the popmenu code will not grow the current selection box wider than this maximum width.
Initializing a TDynamicPopup Instance
The TDynamicPopup code assumes that the resource-based TDynamicPopup instances are created as part of a NewTemplateWindow call. Thus, each TDynamicPopup instance automatically gets an IRes message. The TDynamicPopup code overrides the TPopup.IRes method to initialize the fields specific to TDynamicPopup (see Listing B).
Adding Menu Items
The AddPopItem, AppendPopItem, and CopyItemsFrom methods create menu items for a TDynamicPopup instance (see Listing B).
For AddPopItem, if the parameter afterItemIdx is greater than the number of items in the menu, AddPopItem defaults to AppendPopItem. Otherwise, AddPopItem inserts the new item after the afterItemIdx'th item. If afterItemIdx is 0, the item is inserted before the first item in the menu.
AppendPopItem puts itemText at the end of the popmenu. If there are no actual items in the menu, AppendPopItem inserts itemText as the first and only item.
CopyItemsFrom copies the items of a specified TDynamicPopup to SELF. This method is useful if you want to have two independent popmenus showing the same items. With independent popmenus, the user can select different items for different purposes. For example, the user could select one item as a component to edit now, and select a different item as a default item to use in a simulation later.
Removing Menu Items
To remove items singly, use RemovePopItem. RemovePopItem returns True if the item was actually removed. RemovePopItem returns False if the popmenu is empty or the itemIdx parameter is greater than the number items in the menu.
If the item removed was the currently selected item, RemovePopItem sets the new item selection to the preceding item; if the deleted item was the first item, RemovePopItem sets the item selection to the new first item.
To remove all items at once, use ClearMenuItems.
Whenever a TDynamicPopup instance is cleared (i.e., there are no actual menu items to show), it displays a place-holder item such as “(none)”. TDynamicPopup uses the string in global variable kvNoSelectionStr as the place-holder item.
Typically, you disable and dim a TDynamicPopup instance when it is cleared. In a perfect world, the code to disable/dim and enable/undim the popup view would be part of the TDynamicPopup methods that remove and add items. If this were the case, then the disable/dim and enable/undim response would be transparent to the code that uses TDynamicPopup instances. Unfortunately, achieving the correct redrawing of controls is sometimes tricky; I have had better luck keeping such code at a higher level.
Changing an Item’s Text
To change an item’s text, use ChangeItemText. This method automatically adjusts the size of the popmenu to accommodate the change.
Changing the Current Selection
To change the current selection, you can use SelectItemRelative, SetCurrentItem, or SetCurrentItemByStr. SelectItemRelative changes the selection to the item after the current selection if its parameter next is True, otherwise it changes the selection to the previous item. This method treats the first and last items in a popmenu as adjacent; the item “after” the last item is the first item and vice versa. Also, SelectItemRelative returns the text of the newly selected item in the variable parameter theItem. This method is useful if you want to offer a command-key shortcut for selecting the next or previous item in a TDynamicPopup.
The SetCurrentItem method overrides the TPopup version to set the fOldCurrentItem field. Unfortunately, the TPopup version of SetCurrentItem does not call Focus before it attempts to redraw; TView.AssumeFocus catches this mistake in debugging mode. To add the Focus call, TDynamicPopup.SetCurrentItem includes all the code in TPopup.SetCurrentItem. But this means the code cannot support color, because a required procedure, GetMenuColors, is not available outside of UDialog (i.e., GetMenuColors is not declared in the interface of UDialog). Of course, you can get around this problem by copying GetMenuColors into the unit containing the TDynamicPopup code or by putting its declaration in UDialog.p, thus modifying MacApp; I chose to live without color for now.
SetCurrentItemByStr sets the selected item of a TDynamicPopup instance by name instead of by number. In practice, this capability comes in handy.
Utility Methods
GetItemCount simply returns the current number of items in the TDynamicPopup instance.
IsCleared returns True if there are no actual items (i.e., just the place-holder item) in the TDynamicPopup instance.
ItemSelectionChanged returns True if the currently selected item is different from fOldCurrentItem, which represents the last recognized item selection.
Typically, you use ItemSelectionChanged in a DoChoice implementation. For example, when you get a mPopupHit and identify the originating view (origView) as a TDynamicPopup that you handle, send it a ItemSelectionChanged query. If the result is True, then the mPopupHit was something more than just a click on the current selection. In this way you can use ItemSelectionChange to avoid processing a popmenu selection that didn’t really change the current selection.
The DoRedraw method is used in a number of the other TDynamicPopup methods. It redraws the popmenu in a way that avoids the GetMenuColors procedure (as described above for SetCurrentItem). You shouldn’t need to use DoRedraw outside of the TDynamicPopup code.
The AdjustBotRight method determines the size of the current selection box of the popmenu. The reasons for overriding the TPopup version of this method are explained in the next section.
Blemishes
The TDynamicPopup code has a couple of small visual problems. First, I haven't taken the time to figure out how to properly fix a certain redrawing glitch, so the current selection box only grows (up to a limit) and never shrinks.
Normally, TPopup.AdjustBotRight sizes the current selection box to accommodate the longest item text in the menu. In TDynamicPopup, the longest item can change. In particular, it can get shorter, which is a possibility that TPopup.AdjustBotRight ignores. As a result, TPopup.AdjustBotRight doesn’t always erase a previous current selection box completely; it can leave part of a previously-selected item showing when it draws a new current selection box for a shorter menu item.
After a limited effort to fix the problem, I kludged my way out. I overrode AdjustBotRight and modified the code so it only grows the current selection box (i.e., it never shrinks the current selection box). My modification limits this growth to the width specified in the view template for the popmenu.
This kludge leads to another quirk (which is less glaring…maybe): when the longest item name is replaced by a shorter name, the popmenu code draws the actual menu narrower than the current selection box.
Some day I’ll get around to fixing this redrawing problem properly: if you come up with a fix, please let me know.
The second quirk stems from Toolbox-related mysteries. The TDynamicPopup code goes to some effort to prevent the removal of the last remaining item in a popmenu. Such a deletion (by the Toolbox call DelMenuItem) causes a great crash, showing video flames on the monitor.
I tried to turn this problem into a feature: whenever there are no items in the popmenu, TDynamicPopup displays “(none)” as the current selection. Displaying “(none)” is more explicit than just showing a blank, right?
Wrapping Up
The code for TDynamicPopup will (hopefully) appear in an upcoming MacApp Developers Association Goodies disk. In addition to the source code for TDynamicPopup, the Goodies disk will include a small demo application. The demo application will illustrate how to use the various TDynamicPopup methods and what the final result looks like.
If you have any comments or questions about this paper or about TDynamicPopup, please contact me by mail at 4635 McGuiness, Dexter, MI 48130, or via Compuserve at 71101,3675.
Bibliography
The following is an informal list of articles and books relating to object-oriented programming and MacApp. I hope you find it useful.
Books:
Cox, Brad J. Object Oriented Programming: an Evolutionary Approach. Reading MA: Addison-Wesley, 1986. (Focuses on Objective C.)
Meyer, Bertrand. Object-Oriented Software Construction. Englewood Cliffs, NJ: Prentice-Hall, Inc., 19?? (Focuses on the language Eiffel.)
Schmucker, Kurt J. Object-Oriented Programming. Hasbrouck Heights, NJ: Hayden Book Company, 1986. (Provides a good introduction to MacApp, albeit an early version of MacApp.)
Stroustrup, Bjarne. The C++ Programming Language. Reading, MA: Addison-Wesley Publishing Co., 1986.
Wilson, David A., Larry S.Rosenstein, and Dan Shafer. Programming with MacApp. Reading, MA: Addison-Wesley Publishing Co., 1990. (The latest--second!--book on MacApp, released at the 1990 San Francisco MacWorld Expo. Part of a new series called “Macintosh Inside Out”, edited by Scott Knaster.)
Articles:
from APDAlog:
West, Joel. “Mastering MPW: OOP(s) with C++,” APDAlog, Fall 1989, p8. (Interesting comparisons between Object Pascal, C++, and Think C; also describes how Object Pascal, C++, and C implementations compare, and SADE support.)
Katz, Howard. “OOP Notes: Something Special,” APDAlog, Fall 1989, p13. (a "report" on AAPPS’ MicroTV.)
Burbeck, Steve. “Object-Oriented Programming, A History,” APDAlog, Winter 1989, p17. (see also page 14)
references from AppleDTS Tech Note #239, “Inside Object Pascal”:
• Inside Macintosh, Volume II-53, The Segment Loader
• The Complete MacTutor, Volume 2, “Introduction to Object Pascal”, p. 336
• Macintosh Technical Note #105, MPW Object Pascal Without MacApp
• Macintosh Technical Note #110, MPW: Writing Stand-Alone Code
• Macintosh Technical Note #220, Segment Loader Limitations
from Apple Direct:
Williams, Gregg. “Designing the Future: The Power of Object-Oriented Programming,” Apple Direct, Feb. 1989, p6.
Williams, Gregg. “(Slightly) Inside MacApp,” Apple Direct, May 1989, p12. (Includes a figure of the MacApp class hierarchy.)
from BYTE:
??. “SmallTalk-80 for Mac,” Byte, Jan. 1989, p143.
Thomas, Dave. “What's in an Object?,” Byte, Mar. 1989, p231.
Wegner, Peter. “Learning the Language,” Byte, Mar. 1989, p245. (Discusses OOP in general, touches on several OOP languages.)
Dodani, Hughes, Moshell. “Separation of Powers,” Byte, Mar. 1989, p255. (Discusses MacApp and other toolkits.)
Thompson, Tom. “The Next Step,” Byte, Mar. 1989, p265. (Discusses NeXT's NextStep environment, shows class hierarchy.)
from MacTech Quarterly:
Katz, Howard. “Highly Objectionable: The Think C Library” (a column), MacTech Quarterly, Winter 1989, p102.
Knaster, Scott. “??” (part of an article presenting prognostications), MacTech Quarterly, Spring 1989, p14. (Comments from Scott Knaster promoting OOP.)
Johnson, Ted. “??” (part of an article presenting prognostications), MacTech Quarterly, Spring 1989, p17. (Comments on using OOP to create software components by Ted Johnson of Aldus.)
Leonard, Randy. “OOP: The Future for Macintosh Development, An Introduction to Object-Oriented Programming,” MacTech Quarterly, Spring 1989, p22.
Salmons, Jim, and Timlynn Babitsky. “Prograph: A Turtle Geometric Introduction,” MacTech Quarterly, Spring 1989, p52.
Witten, Steve. “Programming in C++, A Linguistic History,” MacTech Quarterly, Summer 1989, p18. (A good intro to C++.)
Storrie-Lombardi, Michael C. “Smalltalk/V Mac: A New Standard in Object Oriented Programming,” MacTech Quarterly, Summer 1989, p90.
from MacTutor:
Szpakowski, Mark. “MacOOPs!: Prograph raises OOP to new height,” MacTutor, July 1989, p66. (Prograph is verrry interesting; read this article for a good intro.)
Dallas, Alastair. “MacOOPs!: A First Look at THINK C 4.0,” MacTutor, October 1989, p69.
Smith, David. “MacOOPs!: Introduction to MacApp,” MacTutor, Aug. 1989, p10.
Treister, Adam. “C Objects: Pseudo Objects,” MacTutor, Aug. 1989, p38. (the “POOPShell”)
McMath, Chuck and Carl Nelson. “MacApp Workshop: a tale of two quadratic plotters,” MacTutor, Aug. 1989, p56.
Muys-Vasovic, Jean-Denis. “MacOOPs!: Back to the Future: OOP & MacApp,” MacTutor, Sept. 1989, p102.
Doyle, Ken. “Introduction to Object Pascal,” anthologized in The Complete MacTutor, Volume 2 p. 336
Procedure TDynamicPopup.SetCurrentItemByStr( theStr:Str255; VAR item:Integer;
VAR found:Boolean; redraw:Boolean );
Var
idx : Integer;
aStr: Str255;
Begin
For idx := 1 To CountMItems( fMenuHandle ) Do
Begin
GetItemText( idx, aStr );
If IUCompString( aStr, theStr ) = 0 Then
Begin
SetCurrentItem(idx, redraw );
item := idx;
found := True;
Exit( SetCurrentItemByStr );
End;
End;
{ - no match found }
item := 1;
found:= False;
SetCurrentItem(1, redraw );
End;
---------- Footnotes ----------
1. For references to books and articles on MacApp and object programming, see the bibliography at the end of this paper.
2. All MacApp object class names begin with “T” by convention. Although the “T” is arbitrary, the convention is useful because it provides instant recognition of object class names in source code and elsewhere.
3. Defining a new class in Object Pascal is not too different from defining a new Record in Pascal. The first line of the definition is “TDynamicPopup = Object( TPopup),” which specifies that TDynamicPopup is a subclass of TPopup. See the appropriate MPW Pascal or Think Pascal documentation for further details.
4. If you are not familiar with the concepts of overriding methods or calling an inherited method, see one of the general descriptions of MacApp listed in the bibliography.
5. If you are not familiar with the technique of creating views from resource templates in MacApp, look it up in the MacApp documentation or in Programming with MacApp by Dave Wilson, et al (see the bibliography). This technique is one of MacApp’s strong points.
6. The current selection box is the text box that shows the current selection when the actual popmenu is not showing.
7. For an explanation of why there is a maximum width, see “Blemishes” later in this article.
8. For an explanation of the need for a placeholder item, see “Blemishes” later in this article.
9. I’m not sure that this problem still exists; recent experimentation seemed to indicate that deleting the last item was OK. Perhaps a recent MacOS update fixed the problem.
10. Actually, "(none)" can be whatever string you define for the global variable kvNoSelectionStr.
---------- Sidebars ----------
Dynamic Popup Menus in MacApp 2.0ß9
Ralph Krug, P.E.
Abstract:
This paper describes a new class for use with MacApp. The class, called TDynamicPopup, implements a popup menu that can show a dynamic list of items for selection. TDynamicPopup is a subclass of MacApp’s TPopup class, which implements a popup menu with a static list of items. This paper provides a simple example of object-oriented programming with MacApp.
if 'afterItemIdx' is greater than the number of items in the menu, AddPopItem defaults to AppendPopItem; otherwise, AddPopItem inserts the new item after the 'afterItemIdx'th item. If afterItemIdx is 0, the item is inserted before the first item in the menu.
For AppendPopItem, if the popmenu has no actual items (just the dummy string kvNoSelectionStr), AppendPopItem replaces the dummy string with itemText. Otherwise, AppendPopItem adds itemText after the current last item. In both cases, it adjusts the menu to accomodate the new/changed itemtext.
Currently, SetCurrentItem also avoids the TPopup colorizing code that tries to color the menu and its items. The colorizing code colors items by their position within the menu, which, in a TDynamicPopup instance, can change at any time. Thus, adding color to an item in a TDynamicPopup instance doesn’t really add much information (although it might be fun anyway). However, adding color capability to the other components of the popmenu (the title, bar color, the items as a whole, and so on; see IM V-239) should be supported.
--query methods:
GetItemCount : Integer;
IsCleared : Boolean;
ItemSelectionChanged : Boolean;
--utility:
AdjustBotRight; OVERRIDE;
DoRedraw;
mPopupHit:
If (origView = fNamePopup) & (fNamePopup.ItemSelectionChanged) Then